import paramiko
import errno
from expect import is_windows
from expect.exceptions import FailedToConnect
from expect.channel.socket import SocketChannel
from expect.timer import Timer


class SSHChannel(SocketChannel):
    """
    A channel that speaks the SSH protocol with a lot of help from paramiko.
    
    hostname: hostname or IP address to connect to
    port: port to connect on (default 22)
    username: username to use
    password: password to use
    private_key: a paramiko.PKey instance
    timeout: try at least timeout seconds to connect, may go over a bit
    """
    
    def __init__(self, hostname, port=22, username='', password='', private_key=None, timeout=60, **kwargs):
        self.transport = self._connect(hostname, port, username, password, private_key, timeout)
        self.channel = self.transport.open_session()
        
        # start a shell on the remote machine
        self.channel.get_pty()
        self.channel.invoke_shell()
        
        # run the SocketChannel's init class now that we have the socket
        super(SSHChannel, self).__init__(socket=self.channel, **kwargs) 
    
    @classmethod
    def _connect(cls, hostname, port, username, password, private_key, timeout):
        """
        Attempt to connect to the server until 'timeout' seconds have passed.
        """
        timer = Timer(timeout)
        while True:
            try:
                transport = cls._attempt_to_connect(hostname, port, username, password, private_key)
                return transport
            except Exception, e:
                if is_windows:
                    raise Exception("need to define which exceptions to check")
                else:
                    if e.errno == errno.ECONNREFUSED:
                        pass
                    else:
                        raise
            if timer.remaining == 0:
                message = "Failed to connect to '{hostname}' on port '{port}'"
                raise FailedToConnect(message.format(**locals()))
    
    @staticmethod
    def _attempt_to_connect(hostname, port, username, password, private_key):
        """
        Attempt to connect once to the server.
        """
        transport = paramiko.Transport((hostname, port))
        transport.connect(username=username, password=password, pkey=private_key)
        return transport